/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar.p091;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.util.encoders.Hex;

import com.lucky_byte.pay.jar.Crypto;
import com.lucky_byte.pay.jar.Encryptor;

public class Lib091
{
	private static final Logger logger = LogManager.getLogger();

	static public final int WORK_KEY_ALGRO_001 = 1;
	static public final int WORK_KEY_ALGRO_003 = 3;
	static public final int WORK_KEY_ALGRO_004 = 4;

	/**
	 * 解密终端主密钥
	 */
	private static byte[] decryptTMK(String tmk_cipher) {
		if (tmk_cipher.length() % 8 != 0) {
			logger.error("终端主密钥[{}]的长度不为8的倍数，解密终端主密钥失败.");
			return null;
		}
		Encryptor encryptor = Encryptor.getInstance();
		return encryptor.decrypt(Encryptor.WEB_SKEY, tmk_cipher);
	}

	/**
	 * 生成终端工作密钥
	 */
	public static Map<String, byte[]> genWorkKeys(
			int algro, String tmk_cipher) {
		return genWorkKeys(String.format("%03d", algro), tmk_cipher);
	}

	/**
	 * 生成终端工作密钥
	 */
	public static Map<String, byte[]> genWorkKeys(
			String algro, String tmk_cipher) {
		byte[] tmk_clear = decryptTMK(tmk_cipher);
		if (tmk_clear == null) {
			logger.error("解密终端主密钥失败.");
			return null;
		}
		switch (algro) {
		case "001":		// 单倍长密钥算法
			return genWorkKeys001(tmk_clear);
		case "003":		// 双倍长密钥算法
			return genWorkKeys003(tmk_clear);
		case "004":		// 双倍长密钥算法,含磁道密钥
			return genWorkKeys004(tmk_clear);
		default:
			logger.error("终端签到密钥算法[{}]无效.", algro);
			return null;
		}
	}

	/**
	 * 生成终端工作密钥, 单倍长密钥算法
	 */
	private static Map<String, byte[]> genWorkKeys001(byte[] tmk_clear) {
		logger.error("系统不支持通过`单倍长密钥算法'签到，请采用其它算法.");
		return null;
	}

	/**
	 * 生成终端工作密钥, 双倍长密钥算法
	 */
	private static Map<String, byte[]> genWorkKeys003(byte[] tmk_clear) {
		// 生成随机的明文密钥
		byte[] pik = randomKey(16);
		byte[] mak = randomKey(8);

		// 通过终端主密钥加密明文工作密钥
		byte[] pik_e = Crypto.DESEncryptEDE(tmk_clear, pik, 0);
		byte[] mak_e = Crypto.DESEncryptEDE(tmk_clear, mak, 0);

		// 在密文工作密钥后附加 CheckValue，用于下发
		byte[] pik_cv = Hex.decode(packKey(pik_e, pik));
		byte[] mak_cv = Hex.decode(packKey(mak_e, mak));

		Map<String, byte[]> result = new HashMap<>();
		result.put("pik", pik);
		result.put("mak", mak);
		result.put("pik_e", pik_e);
		result.put("mak_e", mak_e);
		result.put("pik_cv", pik_cv);
		result.put("mak_cv", mak_cv);
		return result;
	}

	/**
	 * 生成终端工作密钥, 双倍长密钥算法,含磁道密钥
	 */
	private static Map<String, byte[]> genWorkKeys004(byte[] tmk_clear) {
		// 生成随机的明文密钥
		byte[] pik = randomKey(16);
		byte[] mak = randomKey(8);
		byte[] tdk = randomKey(16);

		// 通过终端主密钥加密明文工作密钥
		byte[] pik_e = Crypto.DESEncryptEDE(tmk_clear, pik, 0);
		byte[] mak_e = Crypto.DESEncryptEDE(tmk_clear, mak, 0);
		byte[] tdk_e = Crypto.DESEncryptEDE(tmk_clear, tdk, 0);

		// 在密文工作密钥后附加 CheckValue，用于下发
		byte[] pik_cv = Hex.decode(packKey(pik_e, pik));
		byte[] mak_cv = Hex.decode(packKey(mak_e, mak));
		byte[] tdk_cv = Hex.decode(packKey(tdk_e, tdk));

		Map<String, byte[]> result = new HashMap<>();
		result.put("pik", pik);
		result.put("mak", mak);
		result.put("tdk", tdk);
		result.put("pik_e", pik_e);
		result.put("mak_e", mak_e);
		result.put("tdk_e", tdk_e);
		result.put("pik_cv", pik_cv);
		result.put("mak_cv", mak_cv);
		result.put("tdk_cv", tdk_cv);
		return result;
	}

	/**
	 * 随机生成密钥
	 */
	private static byte[] randomKey(int size) {
		byte[] res = new byte[size];
		Random random = new Random();
		random.nextBytes(res);
		return res;
	}

	/**
	 * 生成下发密钥格式
	 */
	private static String packKey(byte[] key_e, byte[] key) {
		Encryptor encryptor = Encryptor.getInstance();
		byte[] mac = encryptor.genMAC(Encryptor.MAC_MODE_AX9_9,
				key, new byte[8]);
		String mac_s = new String(Hex.encode(mac));
		String check_value = mac_s.substring(0, 8);
		if (key.length == 16) {
			return new String(Hex.encode(key_e)) + check_value;
		} else {
			return new String(Hex.encode(key_e)) +
					"0000000000000000" + check_value;
		}
	}

	/**
	 * 解密工作密钥，可用于 PIK, MAK, TDK
	 */
	private static byte[] decryptWorkKey(String tmk_cipher, String key_cipher) {
		byte[] tmk_clear = decryptTMK(tmk_cipher);
		if (tmk_clear == null) {
			logger.error("解密工作密钥失败，请检查...");
			return null;
		}
		if (key_cipher.length() % 8 != 0) {
			logger.error("工作密钥[{}]的长度不为8的倍数，请检查...");
			return null;
		}
		// 这里不使用加密机，因为已经得到了解密密钥
		return Crypto.DESDecryptDED(tmk_clear, Hex.decode(key_cipher), 0);
	}

	/**
	 * 验证报文 MAC
	 */
	public static boolean verifyMAC(Packet091 packet,
			String tmk_cipher, String mak_cipher, int mac_algor) {
		byte[] in_bytes = packet.inBytes();
		if (in_bytes == null) {
			logger.error("报文不是从终端或银联返回的报文，不需要验证MAC，请检查...");
			return false;
		}
		if (!packet.has("64")) {
			logger.warn("报文中没有MAC[64]域，不能验证MAC.");
			return false;
		}
		// 获取用于计算 MAC 的 Buffer
		int start = 11;		// 5(tpdu) + 6(head)
		if (packet.getDialANI() != null) {
			start = 44;		// 5(tpdu) + 33(dial) + 6(head)
		}
		if (in_bytes.length <= start + 8) {		// + 8(mac)
			logger.error("报文数据长度小于{}，无法验证报文MAC.", start + 8);
			return false;
		}
		byte[] macbuf = new byte[in_bytes.length - (start + 8)];
		System.arraycopy(in_bytes, start, macbuf, 0,
				in_bytes.length - (start + 8));

		// 解密 MAC 密钥
		byte[] mak_clear = decryptWorkKey(tmk_cipher, mak_cipher);
		if (mak_clear == null) {
			logger.error("解密MAC密钥失败，无法验证报文MAC.");
			return false;
		}
		// 计算 MAC
		Encryptor encryptor = Encryptor.getInstance();
		byte[] mac = encryptor.genMAC(mac_algor, mak_clear, macbuf);
		assert(mac.length == 8);
		byte[] f64 = Hex.decode(packet.get("64"));
		for (int i = 0; i < 8; i++) {
			if (mac[i] != f64[i]) {
				logger.error("验证终端[{}]发送的报文MAC失败，请重新签到.",
						packet.get("41"));
				return false;
			}
		}
		logger.trace("验证报文 MAC 通过 ...");
		return true;
	}

	/**
	 * 计算报文 MAC 并设置到报文中
	 */
	public static boolean setMAC(Packet091 packet,
			String tmk_cipher, String mak_cipher, int mac_algor) {
		// 解密 MAC 密钥
		byte[] mak_clear = decryptWorkKey(tmk_cipher, mak_cipher);
		if (mak_clear == null) {
			logger.error("解密MAC密钥失败，无法验证报文MAC.");
			return false;
		}
		// 将报文转换为字节流，添加一个 MAC 域，这样可以得到正确的 BITMAP，
		// 后续在取 MAC Buffer 时，去除了 MAC 域部分
		packet.set("64", "");
		byte[] bytes = packet.packBytes(false);
		if (bytes.length < 31) {
			logger.error("报文无效，无法计算MAC.");
			return false;
		}
		// 获取 MAC Buffer
		byte[] macbuf = new byte[bytes.length - 19];
		System.arraycopy(bytes, 11, macbuf, 0, bytes.length - 19);

		// 计算 MAC
		Encryptor encryptor = Encryptor.getInstance();
		byte[] mac = encryptor.genMAC(mac_algor, mak_clear, macbuf);
		if (mac == null) {
			logger.error("计算终端[{}]的MAC错误，请检查MAC算法[{}][{}]是否正确.",
					packet.get("41"), mac_algor,
					Encryptor.macModeName(mac_algor));
			return false;
		}
		assert(mac.length == 8);

		// 设置二进制字段时需要展开为字符串
		return packet.set("64", new String(Hex.encode(mac)));
	}
}
